In [44]:
import matplotlib
import random
from tqdm import tqdm
import collections
import math

import datetime
from datetime import date
import pandas as pd
import numpy as np
from plotly import __version__
from plotly.graph_objs import *
#%matplotlib inline

# INITIALIZE PLOTLY TO RUN OFFLINE INSTEAD OF ONLINE
import json
import plotly.offline as pyo
import plotly.graph_objs as go
from plotly.tools import FigureFactory as FF
from plotly import tools

import cufflinks as cf
from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot

init_notebook_mode(connected=True)
cf.go_offline()

init_notebook_mode(connected=True)
In [45]:
class AntClusteringModel:
    def __init__(self, grid_size=200, num_ants=10, num_objects=100, ant_memory_size=50, k1=0.1, k2=0.3, s=10, alpha=1, time_steps=5000000, num_key_frames=100):
        self._grid_size = grid_size
        self._num_ants = num_ants
        self._num_objects = num_objects
        self.k1 = k1
        self.k2 = k2
        self._s = s
        self._alpha = alpha
        self._time_steps = time_steps
        self._num_key_frames = num_key_frames
        self._grid = Grid(grid_size, ant_memory_size, k1, k2, s, alpha)

    def initialize_grid(self):
        self._grid.initialize_objects_randomly(self._num_objects)
        self._grid.initialize_ants_randomly(self._num_ants)

    def simulate(self):
        # make figure
        
        figure = {
            'data': [
                {'x': [], 'y': [], 'xaxis': 'x', 'yaxis': 'y', 'mode': 'markers', 
                 'marker': Marker(color='black'),
                 'name': 'objects'}, 
                {'x': [0], 'y': [0], 'xaxis': 'x2', 'yaxis': 'y2', 'name': 'spatial entropy'}
            ],
            'layout': {
                'xaxis': {'anchor': 'y', 'domain': [0.0, 0.45]},
                'xaxis2': {'anchor': 'y2', 'domain': [0.55, 1.0]},
                'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0]},
                'yaxis2': {'anchor': 'x2', 'domain': [0.0, 1.0]}
            },
            'frames': []
        }

        # fill in most of layout
        figure['layout']['hovermode'] = 'closest'
        figure['layout']['updatemenus'] = [
            {
                'buttons': [
                    {
                        'args': [None, {'frame': {'duration': 500, 'redraw': False},
                                 'fromcurrent': True, 'transition': {'duration': 300, 'easing': 'quadratic-in-out'}}],
                        'label': 'Play',
                        'method': 'animate'
                    },
                    {
                        'args': [[None], {'frame': {'duration': 0, 'redraw': False}, 'mode': 'immediate',
                        'transition': {'duration': 0}}],
                        'label': 'Pause',
                        'method': 'animate'
                    }
                ],
                'direction': 'left',
                'pad': {'r': 10, 't': 87},
                'showactive': False,
                'type': 'buttons',
                'x': 0.1,
                'xanchor': 'right',
                'y': 0,
                'yanchor': 'top'
            }
        ]
        
        spatial_entropy_x = []
        spatial_entropy_y = []
        for i in tqdm(range(self._time_steps)):
            self._grid.update()
            
            if i % 5000 == 0:
                spatial_entropy_x.append(i/50000)
                spatial_entropy_y.append(self._grid.compute_spatial_entropy(10))
                x1 = []
                y1 = []
                x2 = []
                y2 = []
                for obj, k, l in self._grid.get_objects():
                    if obj._t == 0:
                        x1.append(k)
                        y1.append(l)
                    else:
                        x2.append(k)
                        y2.append(l)
                figure['frames'].append(
                    {'data': [
                        {'x': x1, 'xaxis': 'x', 'y': y1, 'yaxis': 'y', 'mode': 'markers', 
                         'marker': Marker(color='red'),
                                'name': 'objects'},
                        {'x': x2, 'xaxis': 'x', 'y': y2, 'yaxis': 'y', 'mode': 'markers', 
                         'marker': Marker(color='blue'),
                                'name': 'objects'},
                        {'x': spatial_entropy_x, 'y': spatial_entropy_y, 'xaxis': 'x2', 'yaxis': 'y2', 'name': 'spatial entropy'}
                    ]})
            
        self._figure = figure
    
    def plot(self):
        pyo.iplot(self._figure)

A model of the Grid

In [46]:
class Grid:
    def __init__(self, size, ant_memory_size, k1, k2, s, alpha):
        self._size = size
        self._ant_memory_size = ant_memory_size
        self._k1 = k1
        self._k2 = k2
        self._s = s
        self._alpha = alpha
        self.initialize_empty_grid()

    def initialize_empty_grid(self):
        self._grid = [[Cell() for i in range(self._size)] for j in range(self._size)]
    
    def initialize_objects_randomly(self, num_initialized_objects):
        initialized_objects = 0
        while initialized_objects < num_initialized_objects and initialized_objects < self._size*self._size:
            i = random.randint(0, self._size-1)
            j = random.randint(0, self._size-1)
            if not self._grid[i][j].has_object():
                # initialize objects with random labels
                self._grid[i][j].set_object(Object(random.randint(0, 1)))
                initialized_objects += 1
    
    def initialize_ants_randomly(self, num_initialized_ants):
        self._ants = []
        position_ants_map = {}
        initialized_ants = 0
        while initialized_ants < num_initialized_ants and initialized_ants < self._size*self._size:
            i = random.randint(0, self._size-1)
            j = random.randint(0, self._size-1)
            if (i, j) not in position_ants_map:
                self._ants.append(Ant(self._ant_memory_size, i, j, self._k1, self._k2))
                position_ants_map[(i, j)] = True
                initialized_ants += 1
                
    def get_neighbour_objects(self, i, j):
        i_min = i-self._s
        i_max = i+self._s
        j_min = j-self._s
        j_max = j+self._s

        if i_min < 0:
            i_min = 0
        if i_max >= self._size:
            i_max = self._size
        if j_min < 0:
            j_min = 0
        if j_max >= self._size:
            j_max = self._size

        neighbour_objects = []
        for k in range(i_min, i_max):
            for l in range(j_min, j_max):
                if self._grid[k][l].has_object():
                    neighbour_objects.append(self._grid[k][l].get_object())
                    
        return neighbour_objects
    
    def compute_frequency(self, i, j):
        f = 0
        obj = self._grid[i][j].get_object()
        for neigh_obj in self.get_neighbour_objects(i, j):
            f += (1 - obj.compute_dissimilarity(neigh_obj)) * 1.0 /self._alpha
        f /= (self._s ** 2)
        return f

    def update(self):
        for ant in self._ants:
            ant.move_randomly(self._size)

            i, j = ant.get_position()
            if ant.has_object():
                if not self._grid[i][j].has_object():
                    # ant already has object, decide whether to drop it
                    self._grid[i][j].set_object(ant.get_object())
                    f = self.compute_frequency(i, j)
                    p = 2 * f
                    if f >= self._k2:
                        p = 1
                    
                    if random.random() < p:
                        # drop
                        self._grid[i][j].set_object(ant.get_object())
                        ant.drop_object()
                    else:
                        self._grid[i][j].remove_object()

            elif self._grid[i][j].has_object():
                # decide wiether to pick up the object
                obj = self._grid[i][j].get_object()
                f = self.compute_frequency(i, j)
                p = (self._k1 / (self._k1 + f)) ** 2
                
                if random.random() < p:
                    # pick
                    obj = self._grid[i][j].get_object()
                    self._grid[i][j].remove_object()
                    ant.pick_object(obj)
                    
    def compute_spatial_entropy(self, s):
        spatial_entropy = 0
        num_objects = self.compute_num_objects()
        for i in range(0, self._size, s):
            for j in range(0, self._size, s):
                p = 0
                for k in range(s):
                    for l in range(s):
                        if self._grid[i+k][j+l].has_object():
                            p += 1
                p /= 1.0 * num_objects
                if p != 0:
                    spatial_entropy -= p * math.log(p)
        return spatial_entropy
                
    def compute_num_objects(self):
        num_objects = 0
        for i in range(self._size):
            for j in range(self._size):
                if self._grid[i][j].has_object():
                    num_objects += 1
        return num_objects
                    
    def get_objects(self):
        objects = []
        for i in range(self._size):
            for j in range(self._size):
                if self._grid[i][j].has_object():
                    objects.append((self._grid[i][j].get_object(), i, j))

        for ant in self._ants:
            if ant.has_object():
                i, j = ant.get_position()
                objects.append((ant.get_object(), i, j))
                
        return objects
    
    def __str__(self):
        return_value = []
        for i in range(self._size):
            return_value.append([])
            for j in range(self._size):
                if self._grid[i][j].has_object():
                    return_value[-1].append(self._grid[i][j].get_object())
                else:
                    return_value[-1].append(-1)
        return str(return_value)


class Cell:
    def __init__(self, object_=None, ant=None):
        self._object = object_

    def has_object(self):
        return self._object != None

    def set_object(self, object_):
        self._object = object_

    def remove_object(self):
        self._object = None

    def get_object(self):
        return self._object
    
    def __str__(self):
        return str(self._t)

A Model of the ant

In [47]:
class Ant:
    def __init__(self, i, j, k1, k2, alpha):
        self.i = i
        self.j = j
        self._k1 = k1
        self._k2 = k2
        self._alpha = alpha
        self._object = None

    def has_object(self):
        return self._object != None
    
    def get_object(self):
        return self._object
    
    def pick_object(self, object_):
        self._object = object_
        
    def drop_object(self):
        self._object = None

    def get_position(self):
        return (self.i, self.j)

    def move_randomly(self, grid_size):
        delta_i = random.randint(-1, 1)
        delta_j = random.randint(-1, 1)
        if self.i + delta_i < grid_size and self.i + delta_i >= 0:
            self.i += delta_i

        if self.j + delta_j < grid_size and self.j + delta_j >= 0:
            self.j += delta_j

class Object:
    def __init__(self, t):
        self._t = t
    
    def compute_dissimilarity(self, obj):
        if self._t == obj._t:
            return 0
        return 1

Simulation

In [50]:
model = AntClusteringModel()
model.initialize_grid()
model.simulate()
100%|██████████| 5000000/5000000 [11:21<00:00, 7340.46it/s] 
In [51]:
model.plot()
In [ ]: